﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Vasuleg.UIDrive
{
    public class UIDriveComputedNode<T> : UIDriveNode<T>
    {
        private readonly Func<T> _computed;
        private readonly HashSet<string> _recordTriggerSet = new();

        internal override void Awake()
        {
            UIDrive.BeginListener(_recordTriggerSet);
            var tValue = _computed();
            UIDrive.EndListener();
            AddRangeMemberPathNodeByTriggerSet(_recordTriggerSet);
            TargetAssign(tValue);
        }

        protected override void OnDriveNoticeDataChange(NoticeDataContext context)
        {
            HashSet<string> tempTriggerSet;
            if (context.CacheComputed.TryGetValue(_computed, out var val) && val is T tVal)
            {
                if (!context.CacheComputedMemberPath.TryGetValue(_computed, out tempTriggerSet))
                {
                    tempTriggerSet = new HashSet<string>();
                }
            }
            else
            {
                tempTriggerSet = new HashSet<string>();
                UIDrive.BeginListener(tempTriggerSet);
                tVal = _computed();
                UIDrive.EndListener();
                tempTriggerSet.RemoveWhere(_recordTriggerSet.Contains);
                context.CacheComputed.Add(_computed, tVal);
                context.CacheComputedMemberPath.Add(_computed, tempTriggerSet);
            }
            TargetAssign(tVal);
            _recordTriggerSet.UnionWith(tempTriggerSet);
            AddRangeMemberPathNodeByTriggerSet(tempTriggerSet);
        }

        private void AddRangeMemberPathNodeByTriggerSet(ICollection<string> triggerSet)
        {
            if (triggerSet.Count == 0)
            {
                return;
            }
            var ownerType = UIDrive.Owner.GetType();
            AddRangeMemberPathNode(triggerSet
                .Select(s => (MemberInfo) ownerType.GetProperty(s))
                .Where(memberInfo => memberInfo != null)
                .Select(memberInfo => new MemberPathNode(UIDrive.Owner, Array.AsReadOnly(new[] {memberInfo}))));
        }

        public UIDriveComputedNode(UIDrive uiDrive, Action<T> targetAssignFunc, Func<T> computed) : base(uiDrive,
            targetAssignFunc)
        {
            _computed = computed;
        }
    }
}